package main

import (
	"context"
	"fmt"
	"io"
	"io/fs"
	"log"
	"math/rand/v2"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"os/signal"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"sync/atomic"
	"syscall"
	"time"
)

const (
	stopOld = "stopOld"
)

var (
	proxyAddr = "127.0.0.1:8080"
	adds      = [...]string{
		"127.0.0.1:8081",
		"127.0.0.1:8082",
		"127.0.0.1:8083",
		"127.0.0.1:8084",
	}
	addr       = ""
	srv        *http.Server
	pid               = os.Getpid()
	oldPid            = 0
	reqNum     uint64 = 0
	holdReqNum uint64 = 0
	inst              = make(chan string)
)

func main() {
	go startServ()
	go listenInstructions()

	oldPid = getPid("main.pid")
	recordPidToFile(pid, "main.pid")

	fmt.Println("oldPid:", oldPid)

	listenStop()

}

func listenInstructions() {
	for {
		switch i := <-inst; i {
		case stopOld:
			sendSig(oldPid, syscall.SIGINT)
		}
	}
}

func isMac() bool {
	return runtime.GOOS == "darwin"
}

func getPid(path string) int {
	pidBytes, _ := os.ReadFile(path)
	p, _ := strconv.Atoi(string(pidBytes))
	return p
}

func recordPidToFile(id int, path string) {
	_ = os.WriteFile(path, []byte(fmt.Sprintf("%d", id)), fs.ModePerm)
}

func sendSig(pid int, sig syscall.Signal) {
	if pid == 0 {
		return
	}

	if err := syscall.Kill(pid, sig); err != nil {
		fmt.Printf("send sig fail %+v\n", err)
	}
}

func listenStop() {
	log.Println("wait stop signal")
	signalChan := make(chan os.Signal, 2)
	signal.Notify(signalChan, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1, syscall.SIGUSR2)

	<-signalChan
	log.Println("receive stop signal wait stop ")

	start := time.Now()
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*60)
	defer cancel()

	log.Println("Server offline ...")
	if err := srv.Shutdown(ctx); err != nil {
		log.Println("Server Shutdown err:", err)
		return
	}

	log.Printf("shutdown cost: %v \n", time.Since(start))
	log.Println("hanlder request num", reqNum)
	log.Println("holdon request num", holdReqNum)

	getFromProxy("/unregister", &url.Values{
		"proxy_pass": []string{addr},
	})
	log.Println("Server exiting")
}

func startServ() {
	fmt.Println("start srv")

	go func() {

		// 等服务成功启动
		time.Sleep(time.Second * 2)

		// confpath := "/etc/nginx/conf.d/test.local.conf"
		// if isMac() {
		// 	confpath = "/usr/local/etc/nginx/servers/test.loc.conf"
		// }
		// if err := ReplaceNgWeight(addr, confpath); err != nil {
		// 	fmt.Println("ReplaceNgWeight failed, ", err)
		// 	return
		// }
		// fmt.Println("restart nginx , stop instruction sending...")
		resgisterAndMoveOtherWeight(addr)
		fmt.Println("resgisterAndMoveOtherWeight, stop instruction sending...")

		inst <- stopOld
	}()

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		atomic.AddUint64(&holdReqNum, 1)
		// 模拟不同的延时的接口
		sle := time.Millisecond * 100 * time.Duration(rand.N(50))
		time.Sleep(sle)
		_, _ = w.Write([]byte(fmt.Sprintf("hello, sleep:%v!", sle)))
		_, _ = w.Write([]byte("-test"))
		atomic.AddUint64(&reqNum, 1)
	})

	for _, addr = range adds {
		srv = &http.Server{Addr: addr}
		fmt.Printf("try listen addr:%s\n", addr)
		if err := srv.ListenAndServe(); err != nil {
			if strings.Contains(err.Error(), "bind: address already in use") {
				continue
			}

			fmt.Printf("listen close: %s, msg:%s, err: %#v\n", addr, err.Error(), err)
			break
		}
	}
	fmt.Println("server stoped")
}

func getFromProxy(path string, urlValues *url.Values) string {
	rawUrl := url.URL{
		Scheme:   "http",
		Host:     proxyAddr,
		Path:     path,
		RawQuery: urlValues.Encode(),
	}

	client := &http.Client{}
	req, err := http.NewRequest(http.MethodGet, rawUrl.String(), strings.NewReader(``))
	if err != nil {
		fmt.Println("create req failed:", err)
		return ""
	}

	res, err := client.Do(req)
	if err != nil {
		fmt.Println("client to proxy failed:", err)
		return ""
	}
	defer res.Body.Close()

	body, err := io.ReadAll(res.Body)
	if err != nil {
		fmt.Println("read from proxy failed", err)
		return ""
	}

	return string(body)
}

func resgisterAndMoveOtherWeight(addr string) {
	body := getFromProxy("/register", &url.Values{
		"proxy_pass": []string{addr},
		"weight":     []string{"1"},
		"clear":      []string{"1"},
	})

	fmt.Println("register addr:", body)
}

func ReplaceNgWeight(addr, path string) error {
	re, err := regexp.Compile(`weight=\d+\s*;`)
	if err != nil {
		return err
	}

	addre, err := regexp.Compile(fmt.Sprintf(`server\s+%s\s+weight=\d+\s*;`, addr))
	if err != nil {
		return err
	}

	b, err := os.ReadFile(path)
	if err != nil {
		return err
	}

	// 全部权重清空
	b = re.ReplaceAll(b, []byte("weight=1;"))

	ok := addre.Match(b)
	fmt.Println("match current server", ok, addre.String())
	if ok {
		fmt.Println("replacing weight...")
		// 仅保留当前监听地址权重
		b = addre.ReplaceAll(b, []byte(fmt.Sprintf("server %s weight=10000;", addr)))
	}

	if err = os.WriteFile(path, b, 0644); err != nil {
		return err
	}

	// 正常执行Go程序是肯定没有这个命令的权限的
	if err = exec.Command("nginx", "-s", "reload").Run(); err != nil {
		return err
	}

	return nil
}
